package com.shiwaixiangcun.authz.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.shiwaixiangcun.authz.ApplicationUtils;
import com.shiwaixiangcun.authz.GeetestLib;
import com.shiwaixiangcun.authz.RabbitComponentNames;
import com.shiwaixiangcun.authz.VerifyCodeUtils;
import com.shiwaixiangcun.authz.config.GeetestConfig;
import com.shiwaixiangcun.authz.dto.VerifyLoginDto;
import com.shiwaixiangcun.authz.service.AccountService;
import com.shiwaixiangcun.core.CoreUtils;
import com.shiwaixiangcun.core.domain.DataSource;
import com.shiwaixiangcun.core.domain.account.*;
import com.shiwaixiangcun.core.domain.file.UploadFileInfo;
import com.shiwaixiangcun.core.domain.oauth.AccessToken;
import com.shiwaixiangcun.core.dto.uploadfile.UploadImageInfoAccessURLDto;
import com.shiwaixiangcun.core.exception.MonkeyException;
import com.shiwaixiangcun.core.plugin.dto.MonkeyDto;
import com.shiwaixiangcun.core.plugin.upload.FileService;
import com.shiwaixiangcun.core.repository.oauth.AccessTokenRepository;
import com.shiwaixiangcun.core.repository.oauth.AccountClientDataRepository;
import com.shiwaixiangcun.core.repository.oauth.AccountRepository;
import com.shiwaixiangcun.core.repository.oauth.AccountUserInfoRepository;
import com.shiwaixiangcun.core.repository.search.SearchOperator;
import com.shiwaixiangcun.core.repository.search.Searchable;
import com.shiwaixiangcun.core.service.BaseServiceImpl;
import com.shiwaixiangcun.core.shiro.exception.*;
import com.shiwaixiangcun.core.sms.util.SMS;
import com.shiwaixiangcun.core.web.response.MonkeyResponseDto;
import com.shiwaixiangcun.utils.MonkeyUtils;
import com.shiwaixiangcun.utils.password.PasswordEncoder;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.*;

/**
 * Created by SilentWu on 2017/2/27.
 */
@Service
public class AccountServiceImpl extends BaseServiceImpl<AccountRepository, Account, Long> implements AccountService {
    private static final String DYNAMIC_PASSWORD_CACHE_NAME = "Dynamic_Password_Cache_Name";
    private static final String ACCOUNT_DEFAULT_PASSWORD = "123456";

    //    @Autowired
//    private Environment environment;
    @Autowired
    private PasswordEncoder passwordEncoder;
    private Cache dynamicPasswordCache;

    @Value("${property.server.host}")
    private String propertyServerHost;

    @Value("${health.server.host}")
    private String healthServerHost;

    @Value("${server.env}")
    private String serverEnv;
    @Autowired
    private AccountClientDataRepository accountClientDataRepository;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RabbitComponentNames rabbitComponentNames;
    @Autowired
    private AccessTokenRepository accessTokenRepository;
    @Autowired
    private AccountUserInfoRepository accountUserInfoRepository;
    @Autowired
    private FileService fileService;

    /**
     * 构造函数.
     *
     * @param repository 注入的Repository
     */
    public AccountServiceImpl(AccountRepository repository, CacheManager cacheManager) {
        super(repository);
        this.dynamicPasswordCache = cacheManager.getCache(DYNAMIC_PASSWORD_CACHE_NAME);
    }


    @Override
    public Account login(String accountStr, String password, Set<String> scopes) {
        validMobileAndPasswordLength(accountStr, password);
        Account account = validAccountIsExist(accountStr);

//        DataSource dataSource = DataSource.PROPERTY;
//        if (scopes.contains(DataSource.HEALTH.getScope())) {
//            dataSource = DataSource.HEALTH;
//        } else if (scopes.contains(DataSource.DIGITAL_LARGE_SCREEN.getScope())) {
//            dataSource = DataSource.DIGITAL_LARGE_SCREEN;
//        }
        DataSource dataSource = getDataSource(scopes);
        AccountClientData accountClientData = accountClientDataRepository.findByAccountIdAndDataSource(account.getId(), dataSource);
        if (!passwordEncoder.matches(password, accountClientData.getPassword())) {
            throw new AccountPasswordNotMatchException();
        }

        validStatusAndScope(scopes, account);
        return account;
    }

    private DataSource getDataSource(Set<String> scopes) {
        DataSource dataSource = null;
        for (DataSource ds : DataSource.values()) {
            if (scopes.contains(ds.getScope())) {
                dataSource = ds;
            }
        }
        if (dataSource == null) {
            throw new MonkeyException("can not found datasource by scope");
        }
        return dataSource;
    }

    @Override
    public Account dynamicPasswordLogin(String mobile, String password, Set<String> scopes) {
        validMobileAndPasswordLength(mobile, password);
//        Account account = validAccountIsExist(mobile);

        if ("production".equals(serverEnv) && !"555666".equals(password)) {
            Cache.ValueWrapper valueWrapper = dynamicPasswordCache.get(mobile);
            if (valueWrapper == null) {
                throw new AccountPasswordNotMatchException();
            }
            String cachePassword = (String) valueWrapper.get();
            if (!password.trim().equals(cachePassword.trim())) {
                throw new AccountPasswordNotMatchException();
            }
            dynamicPasswordCache.evict(mobile);
        }

        Account account = repository.findByAccount(mobile);
        String customerAppScope = DataSource.PROPERTY_CUSTOMER_APP.getScope();
        if (MonkeyUtils.isEmpty(account)) {
            account = buildAccount(mobile, customerAppScope);
        }

        String scope = account.getScope();
        if (!scope.contains(customerAppScope)) {
            account.setScope(scope + "," + customerAppScope);
        }
        repository.save(account);

        AccountUserInfo accountUserInfo = accountUserInfoRepository.findByAccountIdAndDeleted(account.getId(), false);
        if (MonkeyUtils.isEmpty(accountUserInfo)) {
            accountUserInfo = new AccountUserInfo();
            accountUserInfo.setSex(Sex.NONE);
            accountUserInfo.setNick(account.getAccount());
            accountUserInfo.setPhone(account.getAccount());
            accountUserInfo.setAccountId(account.getId());
            accountUserInfoRepository.save(accountUserInfo);
        }

        Map<String, Object> params = new HashMap<>();
        params.put("mobile", mobile);
        params.put("accountId", account.getId());
        String exchange = rabbitComponentNames.getCustomerAppLoginSuccessExchange();
        rabbitTemplate.send(exchange, "", new Message(JSON.toJSONString(params).getBytes(), new MessageProperties()));

//        validStatusAndScope(scopes, account);
        return account;
    }

    @Override
    public MonkeyResponseDto sendDynamicPasswordMessage(String mobile) {
        if ("production".equals(serverEnv)) {
            if (!checkAccount(mobile)) {
                return new MonkeyResponseDto().fail("请填写正确的手机号码");
            }
        }

        String dynamicPassword = CoreUtils.getSix();
        dynamicPasswordCache.put(mobile, dynamicPassword);
//        cache.put(mobile, false);
        //TODO 短信模版的更改
        SMS.sendPassword(mobile, Integer.parseInt(dynamicPassword));

        return new MonkeyResponseDto().success();
    }

    @Override
    public Account getAccount(String accessToken) {
        AccessToken token = accessTokenRepository.findByTokenId(accessToken);
        return repository.findByAccount(token.getAccount());
    }
    /*获取账户的用户信息*/
    @Override
    public Map<String, Object> getAccountUserInfo() {
        Map<String, Object> detailMap = new HashMap<>();
        AccountUserInfo accountUserInfo = accountUserInfoRepository.findByAccountIdAndDeleted(CoreUtils.getCurrentAccount().getId(), false);
        detailMap.put("name", accountUserInfo.getNick());
        detailMap.put("sex", accountUserInfo.getSex());
        detailMap.put("phone", accountUserInfo.getPhone());
        detailMap.put("birthday", accountUserInfo.getBirthday());
        detailMap.put("propertyAuth", false);
        detailMap.put("healthAuth", false);
        detailMap.put("companyPhone", "");
        setAvatarParams(accountUserInfo, detailMap);
        return detailMap;
    }

    @Override
    public void modifyUserInfo(MonkeyDto monkeyDto) {
        AccountUserInfo accountUserInfo = accountUserInfoRepository.findByAccountIdAndDeleted(CoreUtils.getCurrentAccount().getId(), false);
        if (MonkeyUtils.isNotEmpty(monkeyDto.getString("name"))) {
            accountUserInfo.setNick(monkeyDto.getString("name"));
        }
        if (MonkeyUtils.isNotEmpty(monkeyDto.getString("sex"))) {
            accountUserInfo.setSex(Sex.valueOf(monkeyDto.getString("sex")));
        }
        if (MonkeyUtils.isNotEmpty(monkeyDto.getString("phone"))) {
            accountUserInfo.setPhone(monkeyDto.getString("phone"));
        }
        if (MonkeyUtils.isNotEmpty(monkeyDto.getDate("birthday"))) {
            accountUserInfo.setBirthday(monkeyDto.getDate("birthday"));
        }
        dealWithAvatar(accountUserInfo, monkeyDto);
        accountUserInfoRepository.save(accountUserInfo);

//        Map<String, Object> params = new HashMap<>();
//        params.put("nickName", accountUserInfo.getNick());
//        params.put("accountId", accountUserInfo.getAccountId());
//        String exchange = rabbitComponentNames.modifyCustomerNikeName();
//        rabbitTemplate.send(exchange, "", new Message(JSON.toJSONString(params).getBytes(), new MessageProperties()));
//        logger.info("消息已发出");
//        //以下代码针对于又是消息队列失效，这边消息已经发出，但是商城那边接收不到消息。后面解决这个问题后 删除下面的代码（为了晚上发版临时处理）
//        repository.updateMarketCustomerNick(customer.getAccountId(), customer.getNick());
    }

    @Override
    public void setPushAlias(String pushAlias) {
        AccountUserInfo accountUserInfo = accountUserInfoRepository.findByAccountIdAndDeleted(CoreUtils.getCurrentAccount().getId(), false);
        accountUserInfo.setPushAlias(pushAlias);
        accountUserInfoRepository.save(accountUserInfo);

//        Map<String, Object> params = new HashMap<>();
//        params.put("pushAlias", pushAlias);
//        params.put("accountId", customer.getAccountId());
//        String exchange = rabbitComponentNames.updatePushAliasSuccessPropertyQueue();
//        rabbitTemplate.send(exchange, "", new Message(JSON.toJSONString(params).getBytes(), new MessageProperties()));
    }

    @Override
    public String uploadHeadImage(MultipartFile file) {
        AccountUserInfo accountUserInfo = accountUserInfoRepository.findByAccountIdAndDeleted(CoreUtils.getCurrentAccount().getId(), false);
        // 获取以前的头像 -- 上传完成后删除
        List<UploadFileInfo> headImageList = fileService.findByTargetIdAndField(accountUserInfo.getId(), "accountUserInfoAvatar");
        UploadFileInfo oldHeadImage = null;
        if (MonkeyUtils.isNotEmpty(headImageList)) {
            oldHeadImage = headImageList.get(0);
        }
        // 头像满足的格式
        String[] format = new String[]{"png", "jpg", "jpeg"};
        boolean isAllowFormat = fileService.checkFileType(file.getOriginalFilename(), Arrays.asList(format));
        if (!isAllowFormat) {
            throw new MonkeyException("只能支持 :png,jpg,jpeg 格式");
        }
        // 存储新上传的文件
        UploadFileInfo store = fileService.storeImageAndCrtThumbImage(file);
        store.setTargetId(accountUserInfo.getId());
        store.setTargetField("accountUserInfoAvatar");
        fileService.update(store);
        // 上传完成  删除以前的头像
        if (oldHeadImage != null) {
            fileService.deleteFile(oldHeadImage);
        }
        accountUserInfo.setAvatar(store.getPath());
        accountUserInfoRepository.update(accountUserInfo);

//        Map<String, Object> params = new HashMap<>();
//        params.put("avatar", store.getPath());
//        params.put("accountId", accountUserInfo.getAccountId());
//        String exchange = rabbitComponentNames.updateAvatarSuccessPropertyQueue();
//        rabbitTemplate.send(exchange, "", new Message(JSON.toJSONString(params).getBytes(), new MessageProperties()));

        return fileService.getAccessURL(store.getPath());
    }

    @Override
    public Boolean verifyLogin(HttpServletRequest request, HttpServletResponse response, VerifyLoginDto verifyLoginDto) {
        String ip = ApplicationUtils.getIpAddr(request);
        GeetestLib gtSdk = new GeetestLib(GeetestConfig.getGeetest_id(), GeetestConfig.getGeetest_key(),
                GeetestConfig.isnewfailback());

        String challenge = verifyLoginDto.getChallenge();
        String validate = verifyLoginDto.getValidate();
        String seccode = verifyLoginDto.getSeccode();

        int gtServerStatusCode = (Integer) request.getSession().getAttribute(gtSdk.gtServerStatusSessionKey);
        String userId = (String) request.getSession().getAttribute("userId");

        HashMap<String, String> param = new HashMap<>();
        param.put("user_id", userId);
        param.put("client_type", "web");
        param.put("ip_address", ip);
        int gtResult;
        if (gtServerStatusCode == 1) {
            gtResult = gtSdk.enhencedValidateRequest(challenge, validate, seccode, param);
        } else {
            gtResult = gtSdk.failbackValidateRequest(challenge, validate, seccode);
        }
        if (gtResult == 1) {
            sendDynamicPasswordMessage(verifyLoginDto.getMobile());
            return true;
        }

        return false;
    }

    @Override
    public Object startCaptcha(HttpServletRequest request) {
        String ipAddr = ApplicationUtils.getIpAddr(request);
        GeetestLib gtSdk = new GeetestLib(GeetestConfig.getGeetest_id(), GeetestConfig.getGeetest_key(),
                GeetestConfig.isnewfailback());

        String userId = MonkeyUtils.uuid();
        HashMap<String, String> param = new HashMap<>();
        param.put("user_id", userId);
        param.put("client_type", "web"); //web:电脑上的浏览器；h5:手机上的浏览器，包括移动应用内完全内置的web_view；native：通过原生SDK植入APP应用的方式
        param.put("ip_address", ipAddr);

        int gtServerStatus = gtSdk.preProcess(param);
        HttpSession session = request.getSession();
        session.setAttribute(gtSdk.gtServerStatusSessionKey, gtServerStatus);
        session.setAttribute("userId", userId);

        return JSONObject.parse(gtSdk.getResponseStr());
    }

    @Override
    public void verifyCode(HttpServletRequest request, HttpServletResponse response) {
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        response.setContentType("image/jpeg");
        //生成随机字串
        String verifyCode = VerifyCodeUtils.generateVerifyCode(4);
        HttpSession session = request.getSession();
        session.setAttribute("codeKey", verifyCode);
        //生成图片
        int w = 120, h = 50;
        try {
            ServletOutputStream os = response.getOutputStream();
            VerifyCodeUtils.outputImage(w, h, os, verifyCode);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void dealWithAvatar(AccountUserInfo accountUserInfo, MonkeyDto monkeyDto) {
        Long imageId = monkeyDto.getLong("imageId");
        if (MonkeyUtils.isNotEmpty(imageId)) {
            UploadFileInfo file = fileService.findOne(imageId);
            accountUserInfo.setAvatar(file.getPath());
            fileService.tempFileStore(file.getId());
        }
    }

    private void setAvatarParams(AccountUserInfo accountUserInfo, Map<String, Object> detailMap) {
        if (MonkeyUtils.isEmpty(accountUserInfo.getAvatar())) {
            detailMap.put("avatar", new UploadImageInfoAccessURLDto());
            return;
        }
        Searchable searchable = Searchable.newSearchable();
        searchable.addSearchFilter("path", SearchOperator.eq, accountUserInfo.getAvatar());
        List<UploadFileInfo> images = fileService.findAll(searchable).getContent();
        UploadImageInfoAccessURLDto thumbImage = new UploadImageInfoAccessURLDto();
        for (UploadFileInfo file : images) {
            thumbImage.setFileId(file.getId());
            thumbImage.setAccessUrl(fileService.getAccessURL(file.getPath()));
            thumbImage.setThumbImageURL(fileService.getThumbImageURL(file.getPath(), file.getThumbImageWidth(), file.getThumbImageHeight()));
            detailMap.put("avatar", thumbImage);
            break;
        }
    }

    private Account buildAccount(String mobile, String customerAppScope) {
        Account account = new Account();
        account.setAccount(mobile);
        account.setScope(customerAppScope);
        repository.save(account);

        AccountClientData accountClientData = new AccountClientData();
        accountClientData.setAccountId(account.getId());
        accountClientData.setDataSource(DataSource.PROPERTY);
        accountClientData.setPassword(passwordEncoder.encode(ACCOUNT_DEFAULT_PASSWORD));
        accountClientData.setStatus(AccountStatus.NORMAL);
        accountClientDataRepository.save(accountClientData);
        return account;
    }

    private void validStatusAndScope(Set<String> scopes, Account account) {
        DataSource dataSource = getDataSource(scopes);
        AccountClientData accountClientData = accountClientDataRepository.findByAccountIdAndDataSource(account.getId(), dataSource);

        if (AccountStatus.BLOCKED.equals(accountClientData.getStatus())) {
            throw new AccountBlockedException("blocked account,contact administrator");
//            throw new AccountBlockedException(userStatusHistoryService.getLastReason(user));
        }

        String scope = account.getScope();
        if (scopes.isEmpty() || MonkeyUtils.isEmpty(scope) || excludeScopes(scopes, scope)) {
            throw new AccountScopeException();
        }
    }

    private Account validAccountIsExist(String accountStr) {
        Account account = repository.findByAccount(accountStr);
        if (account == null) {
            throw new AccountNotExistsException();
        }
        return account;
    }

    private void validMobileAndPasswordLength(String accountStr, String password) {
        if (StringUtils.isEmpty(accountStr) || StringUtils.isEmpty(password)) {
            throw new AccountNotExistsException();
        }
        //密码如果不在指定范围内 肯定错误
        if (password.length() < Account.PASSWORD_MIN_LENGTH || password.length() > Account.PASSWORD_MAX_LENGTH) {
            throw new AccountPasswordNotMatchException();
        }

        if ("production".equals(serverEnv)) {
            if (!checkAccount(accountStr)) {
                throw new AccountException("please input your mobile number");
            }
        }
    }

    private boolean checkAccount(String username) {
        return username.matches(Account.MOBILE_PATTERN);
    }

    protected boolean excludeScopes(Set<String> scopes, String allowScope) {
        for (String scope : scopes) {
            if (!allowScope.contains(scope)) {
                return true;
            }
        }
        return false;
    }

}
